import tkinter as tk
from tkinter import filedialog, ttk
from PIL import Image, ImageTk
import easyocr
import threading
import os
import numpy as np
import re
import csv
import traceback
from collections import defaultdict
from datetime import datetime
import pandas as pd


class OCRBatchProcessor:
    def __init__(self, root):
        self.root = root
        self.root.title("OCR批量验证工具")
        self.root.geometry("1100x800")

        # 初始化EasyOCR阅读器缓存
        self.reader_cache = {}
        self.lang_markers = {
            'en': 'en',  # 英语
            'it': 'it',  # 意大利语
            'de': 'de',  # 德语
            'fr': 'fr',  # 法语
            'pt': 'pt',  # 葡萄牙语
            'pl': 'pl',  # 波兰语
            'es': 'es',  # 西班牙语
            'ja': 'ja',  # 日语
            'ko': 'ko',  # 韩语
            'zh-cn': 'ch_sim',  # 简体中文
            'zh-tw': 'ch_tra'  # 繁体中文
        }

        # 混合语言支持（日语+英语）
        self.mixed_languages = {
            'ja': ['ja', 'en'],  # 日语图片同时使用日语和英语模型
            'it': ['it', 'en'],
            'de': ['de', 'en'],
            'fr': ['fr', 'en'],
            'pt': ['pt', 'en'],
            'pl': ['pl', 'en'],
            'es': ['es', 'en'],
            'ko': ['ko', 'en'],
            'ch_sim': ['ch_sim', 'en'],
            'ch_tra': ['ch_tra', 'en']
        }

        # 存储数据
        self.image_dir = ""
        self.csv_path = ""
        self.ocr_results = defaultdict(str)  # 存储图片文件名到识别文本的映射
        self.csv_data = []  # 存储CSV文件中的数据
        self.results = []  # 存储验证结果

        # 新增：存储日志位置信息
        self.log_positions = {}  # 图片文件名 -> 日志起始位置

        # 创建界面
        self.create_widgets()

        # 统计变量
        self.total_count = 0
        self.success_count = 0
        self.fail_count = 0

    def create_widgets(self):
        # 主框架
        main_frame = tk.Frame(self.root)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # 输入区域
        input_frame = tk.LabelFrame(main_frame, text="输入设置", padx=10, pady=10)
        input_frame.pack(fill=tk.X, padx=5, pady=5)

        # 图片文件夹选择
        img_dir_frame = tk.Frame(input_frame)
        img_dir_frame.pack(fill=tk.X, pady=5)

        tk.Label(img_dir_frame, text="图片文件夹:").pack(side=tk.LEFT)
        self.img_dir_entry = tk.Entry(img_dir_frame, width=50)
        self.img_dir_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
        tk.Button(img_dir_frame, text="浏览", command=self.browse_image_dir).pack(side=tk.LEFT)

        # CSV文件选择
        csv_file_frame = tk.Frame(input_frame)
        csv_file_frame.pack(fill=tk.X, pady=5)

        tk.Label(csv_file_frame, text="表格文件:").pack(side=tk.LEFT)
        self.csv_entry = tk.Entry(csv_file_frame, width=50)
        self.csv_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
        tk.Button(csv_file_frame, text="浏览", command=self.browse_csv_file).pack(side=tk.LEFT)

        # 处理按钮区域
        btn_frame = tk.Frame(input_frame)
        btn_frame.pack(fill=tk.X, pady=10)

        self.process_btn = tk.Button(btn_frame, text="开始处理", command=self.start_processing, width=15)
        self.process_btn.pack(side=tk.LEFT, padx=5)

        self.stop_btn = tk.Button(btn_frame, text="停止", command=self.stop_processing, state=tk.DISABLED, width=15)
        self.stop_btn.pack(side=tk.LEFT, padx=5)

        # 自动修正选项
        self.auto_correct_var = tk.BooleanVar(value=True)
        tk.Checkbutton(btn_frame, text="自动修正大小写", variable=self.auto_correct_var).pack(side=tk.LEFT, padx=10)

        # 多语言识别选项
        self.mixed_lang_var = tk.BooleanVar(value=True)
        tk.Checkbutton(btn_frame, text="启用多语言识别", variable=self.mixed_lang_var).pack(side=tk.LEFT, padx=10)

        # 新增：结果和日志的水平布局容器
        result_log_frame = tk.Frame(main_frame)
        result_log_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        # 结果表格区域
        result_frame = tk.LabelFrame(result_log_frame, text="验证结果", padx=10, pady=10)
        result_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5)

        # 创建表格
        columns = ("text", "file", "img", "status", "language")
        self.tree = ttk.Treeview(result_frame, columns=columns, show="headings", height=15)

        # 设置列宽和标题（调整为更紧凑的宽度）
        self.tree.column("text", width=250, anchor=tk.W)  # 预期文本：减少到250
        self.tree.column("file", width=100, anchor=tk.W)  # CSV文件列：减少到100
        self.tree.column("img", width=100, anchor=tk.W)   # 图片文件名：减少到100
        self.tree.column("status", width=60, anchor=tk.CENTER)  # 状态：减少到60
        self.tree.column("language", width=60, anchor=tk.CENTER)  # 语言：减少到60

        self.tree.heading("text", text="预期文本")
        self.tree.heading("file", text="CSV文件列")
        self.tree.heading("img", text="图片文件名")
        self.tree.heading("status", text="状态")
        self.tree.heading("language", text="语言")

        # 添加滚动条
        scrollbar = ttk.Scrollbar(result_frame, orient=tk.VERTICAL, command=self.tree.yview)
        self.tree.configure(yscrollcommand=scrollbar.set)

        # 布局
        self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        # 新增：绑定选择事件
        self.tree.bind('<<TreeviewSelect>>', self.on_tree_select)

        # 日志区域
        log_frame = tk.LabelFrame(result_log_frame, text="处理日志", padx=10, pady=10)
        log_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5)

        self.log_text = tk.Text(log_frame, height=15, width=50)  # 调整宽度以适应水平布局
        self.log_text.pack(fill=tk.BOTH, expand=True)

        # 新增：配置高亮标签
        self.log_text.tag_configure("highlight", background="yellow", foreground="black")

        # 配置日志标签
        self.log_text.tag_configure("ocr_log", font=("Arial", 10, "bold"), foreground="blue")
        self.log_text.tag_configure("recognition_result", font=("Arial", 10), background="#f0f0f0")

        # 统计信息区域
        stats_frame = tk.Frame(main_frame)
        stats_frame.pack(fill=tk.X, padx=5, pady=10)

        self.total_label = tk.Label(stats_frame, text="总处理数: 0", font=("Arial", 10))
        self.total_label.pack(side=tk.LEFT, padx=10)

        self.success_label = tk.Label(stats_frame, text="成功数: 0", fg="green", font=("Arial", 10))
        self.success_label.pack(side=tk.LEFT, padx=10)

        self.fail_label = tk.Label(stats_frame, text="失败数: 0", fg="red", font=("Arial", 10))
        self.fail_label.pack(side=tk.LEFT, padx=10)

        # 进度条
        self.progress_var = tk.DoubleVar()
        progress_frame = tk.Frame(main_frame)
        progress_frame.pack(fill=tk.X, padx=5, pady=5)

        tk.Label(progress_frame, text="进度:").pack(side=tk.LEFT)
        self.progress_bar = ttk.Progressbar(progress_frame, variable=self.progress_var, maximum=100)
        self.progress_bar.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)

        # 状态栏
        self.status_var = tk.StringVar()
        self.status_var.set("就绪")
        status_bar = tk.Label(self.root, textvariable=self.status_var, bd=1, relief=tk.SUNKEN, anchor=tk.W)
        status_bar.pack(side=tk.BOTTOM, fill=tk.X)

        # 停止处理标志
        self.stop_processing_flag = False

    # 新增：处理表格选择事件
    def on_tree_select(self, event):
        selected_items = self.tree.selection()
        if not selected_items:
            return

        item = selected_items[0]
        values = self.tree.item(item, "values")
        if len(values) >= 3:  # 确保有足够的列
            img_file = values[2]  # 图片文件名在第三列

            # 在日志中查找该图片的识别记录
            if img_file in self.log_positions:
                start_index = self.log_positions[img_file]

                # 启用日志文本以便修改
                self.log_text.config(state=tk.NORMAL)

                # 移除旧的高亮
                self.log_text.tag_remove("highlight", "1.0", tk.END)

                # 添加新高亮
                self.log_text.tag_add("highlight", start_index, f"{start_index} lineend")

                # 滚动到高亮位置
                self.log_text.see(start_index)

                # 禁用日志文本
                self.log_text.config(state=tk.DISABLED)

    def browse_image_dir(self):
        dir_path = filedialog.askdirectory()
        if dir_path:
            self.img_dir_entry.delete(0, tk.END)
            self.img_dir_entry.insert(0, dir_path)

    def browse_csv_file(self):
        """
        选择表格文件（支持 CSV / XLS / XLSX）。
        优先从上次打开目录或图片目录打开，避免进入 Windows 的 Quick Access。
        选择后会更新 self.csv_entry / self.csv_path 并保存 last_open_dir。
        """
        # 尝试构造一个合理的 initialdir，按优先级：上次打开目录 -> 图片目录输入 -> 用户主目录 -> cwd
        initial = None

        # 1) 上次打开的目录（若已保存）
        initial = getattr(self, "last_open_dir", None)

        # 2) 图片目录输入框（如果存在且有效）
        if not initial:
            try:
                img_dir = self.img_dir_entry.get().strip()
                if img_dir and os.path.isdir(img_dir):
                    initial = img_dir
            except Exception:
                pass

        # 3) 如果之前文本框里有 csv 路径，则用它的父目录
        if not initial:
            try:
                cur_csv = self.csv_entry.get().strip()
                if cur_csv:
                    parent = os.path.dirname(os.path.expanduser(cur_csv))
                    if parent and os.path.isdir(parent):
                        initial = parent
            except Exception:
                pass

        # 4) 最后备用：用户主目录或当前工作目录
        if not initial:
            initial = os.path.expanduser("~")
            if not os.path.isdir(initial):
                initial = os.getcwd()

        path = filedialog.askopenfilename(
            title="选择表格文件（CSV / XLS / XLSX）",
            filetypes=[
                ("表格文件", ("*.csv", "*.xls", "*.xlsx")),
                ("CSV 文件", "*.csv"),
                ("Excel 文件", ("*.xls", "*.xlsx")),
                ("所有文件", "*.*"),
            ],
            initialdir=initial
        )

        if not path:
            return

        # 规范并保存路径
        path = os.path.expanduser(path).strip()
        path = os.path.normpath(path)
        self.csv_path = path

        # 更新 UI（尽量兼容你的命名）
        try:
            self.csv_entry.delete(0, tk.END)
            self.csv_entry.insert(0, path)
        except Exception:
            pass
        try:
            if hasattr(self, "csv_path_var"):
                self.csv_path_var.set(path)
        except Exception:
            pass

        # 记录已打开目录，供下次作为 initialdir
        try:
            self.last_open_dir = os.path.dirname(path)
        except Exception:
            self.last_open_dir = None

        self.log_message(f"已选择文件: {path}")

    def log_message(self, message, is_ocr_log=False, is_recognition_result=False):
        self.log_text.config(state=tk.NORMAL)
        timestamp = datetime.now().strftime("%H:%M:%S")

        # 插入前记录起始位置
        start_index = self.log_text.index("end-1c")

        self.log_text.insert(tk.END, f"[{timestamp}] {message}\n")

        # 插入后记录结束位置
        end_index = self.log_text.index("end-1c")

        # 应用不同的标签样式
        if is_ocr_log:
            self.log_text.tag_add("ocr_log", start_index, end_index)
        elif is_recognition_result:
            self.log_text.tag_add("recognition_result", start_index, end_index)

        self.log_text.see(tk.END)
        self.log_text.config(state=tk.DISABLED)

        # 新增：如果是识别图片的消息，记录位置
        if "识别图片" in message:
            # 提取图片文件名
            img_file = message.split("识别图片")[1].split()[0].strip()
            self.log_positions[img_file] = start_index

    def start_processing(self):
        # 读取并规范化路径
        self.image_dir = self.img_dir_entry.get().strip()
        self.csv_path = os.path.expanduser(self.csv_entry.get().strip())
        self.csv_path = os.path.normpath(self.csv_path)

        # 图片文件夹检查
        if not os.path.isdir(self.image_dir):
            self.status_var.set("错误: 图片文件夹路径无效")
            self.log_message("错误: 图片文件夹路径无效")
            return

        # 表格文件存在性检查（更宽容的检查并支持 xls/xlsx）
        if not self.csv_path or not os.path.exists(self.csv_path) or not os.path.isfile(self.csv_path):
            self.status_var.set("错误: 表格文件路径无效")
            self.log_message("错误: 表格文件路径无效")
            return

        # 后缀检查，避免误选不支持的类型
        _, ext = os.path.splitext(self.csv_path.lower())
        if ext not in ('.csv', '.xls', '.xlsx'):
            self.status_var.set("错误: 不支持的文件类型（仅支持 .csv / .xls / .xlsx）")
            self.log_message("错误: 不支持的文件类型（仅支持 .csv / .xls / .xlsx）")
            return

        # 重置状态
        self.stop_processing_flag = False
        self.ocr_results.clear()
        self.csv_data = []
        self.results = []
        self.tree.delete(*self.tree.get_children())
        self.total_count = 0
        self.success_count = 0
        self.fail_count = 0
        self.update_stats()

        # 禁用按钮
        self.process_btn.config(state=tk.DISABLED)
        self.stop_btn.config(state=tk.NORMAL)

        # 在新线程中处理
        threading.Thread(target=self.process_data, daemon=True).start()

    def stop_processing(self):
        self.stop_processing_flag = True
        self.status_var.set("正在停止处理...")
        self.log_message("用户请求停止处理...")

    def update_stats(self):
        self.total_label.config(text=f"总处理数: {self.total_count}")
        self.success_label.config(text=f"成功数: {self.success_count}")
        self.fail_label.config(text=f"失败数: {self.fail_count}")

    def get_language_from_filename(self, filename):
        """根据文件名中的语言标记确定语言列表"""
        filename_lower = filename.lower()
        for marker, lang_code in self.lang_markers.items():
            if marker in filename_lower:
                # 如果启用了多语言识别并且该语言有混合语言配置
                if self.mixed_lang_var.get() and lang_code in self.mixed_languages:
                    return self.mixed_languages[lang_code]
                return [lang_code]
        # 默认使用英语
        return ['en']

    def get_reader(self, lang_list):
        """获取指定语言列表的OCR阅读器（使用缓存）"""
        # 将语言列表排序并转换为元组作为缓存键
        lang_key = tuple(sorted(lang_list))
        if lang_key not in self.reader_cache:
            # 如果语言列表中包含英语且只有一个语言，则使用english_g2网络
            if len(lang_list) == 1 and lang_list[0] == 'en':
                # 单一英语
                self.reader_cache[lang_key] = easyocr.Reader(
                    lang_list,
                    gpu=False,
                    recog_network='english_g2',
                    model_storage_directory='model',
                    download_enabled=True
                )
            else:
                # 多语言或非英语单语言，不指定recog_network（使用默认）
                self.reader_cache[lang_key] = easyocr.Reader(
                    lang_list,
                    gpu=False,
                    model_storage_directory='model',
                    download_enabled=True
                )
            self.log_message(f"已加载语言模型: {', '.join(lang_list)}")
        return self.reader_cache[lang_key]

    def read_table_file(self, path):
        """
        根据文件扩展名读取 CSV 或 Excel，返回 list of dict（每行一个 dict）。
        保证返回的行中至少有 'text' 和 'file' 键（若不存在会被检测到后报错）。
        """
        _, ext = os.path.splitext(path.lower())
        if ext in ('.xls', '.xlsx'):
            # 读取 excel
            try:
                # 读取第一个 sheet
                df = pd.read_excel(path, sheet_name=0, engine=None)  # pandas 自动选择 engine（openpyxl/xlrd）
            except Exception as e:
                # 尝试显式指定 engine（更宽容）
                try:
                    if ext == '.xlsx':
                        df = pd.read_excel(path, sheet_name=0, engine='openpyxl')
                    else:
                        df = pd.read_excel(path, sheet_name=0, engine='xlrd')
                except Exception as e2:
                    raise RuntimeError(f"读取 Excel 文件失败: {e} / {e2}")
            # 将 NaN 替换为空字符串，转换为 list of dict
            df = df.fillna('')
            records = df.to_dict(orient='records')
            # 确保键是字符串且小写化（保持与你 CSV 代码一致）
            new_records = []
            for r in records:
                nr = {}
                for k, v in r.items():
                    if k is None:
                        continue
                    nk = str(k).strip()
                    nr[nk] = '' if pd.isna(v) else str(v)
                new_records.append(nr)
            return new_records

        elif ext == '.csv':
            # CSV 使用原来的编码尝试逻辑
            encodings = ['utf-8', 'gbk', 'utf-8-sig', 'cp1252']
            csvfile = None
            last_exception = None
            for enc in encodings:
                try:
                    csvfile = open(path, 'r', encoding=enc)
                    reader = csv.DictReader(csvfile)
                    original_data = list(reader)
                    csvfile.close()
                    # 将 None 或缺失字段填为空字符串，保证返回格式一致
                    normalized = []
                    for row in original_data:
                        nr = {}
                        for k, v in row.items():
                            nk = '' if k is None else str(k).strip()
                            nr[nk] = '' if v is None else str(v)
                        normalized.append(nr)
                    return normalized
                except UnicodeDecodeError as ude:
                    last_exception = ude
                    if csvfile:
                        csvfile.close()
                    continue
                except Exception as e:
                    last_exception = e
                    if csvfile:
                        csvfile.close()
                    break
            raise RuntimeError(f"无法读取 CSV 文件，尝试的编码失败: {last_exception}")

        else:
            raise RuntimeError("不支持的文件格式（仅支持 .csv / .xls / .xlsx）")

    def insert_initial_rows(self):
        """读取完 csv_data 后立即把每一行插入表格，状态为 '等待处理'，并建立索引映射"""
        # 清空现有
        for item in self.tree.get_children():
            self.tree.delete(item)

        self.row_items = []  # index -> tree item id
        self.rows_by_file = defaultdict(list)  # file -> list of row indices

        for i, row in enumerate(self.csv_data):
            expected_text = row.get('text', '').strip()
            img_file = row.get('file', '').strip()

            # 跳过空白行
            if not expected_text and not img_file:
                continue

            lang_list = self.get_language_from_filename(img_file) if img_file else ['en']
            language_str = ', '.join(lang_list)

            # 插入行，初始状态等待处理
            item_id = self.tree.insert("", "end", values=(
                expected_text, img_file, img_file, "等待处理", language_str
            ), tags=('neutral',))
            self.row_items.append(item_id)

            # 记录 file -> row index 映射（空 file 也记录）
            self.rows_by_file[img_file].append(i)

        # 颜色配置（中性/成功/失败）
        self.tree.tag_configure('green', foreground='green')
        self.tree.tag_configure('red', foreground='red')
        self.tree.tag_configure('neutral', foreground='gray')

        # 刷新统计（尚未处理任何行）
        self.root.after(0, self.update_stats)

    def _apply_row_update(self, index, status, color):
        """在主线程更新单行显示（由 root.after 调用）"""
        try:
            item_id = self.row_items[index]
        except Exception:
            return

        row = self.csv_data[index]
        expected_text = row.get('text', '')
        img_file = row.get('file', '')
        language_str = ', '.join(self.get_language_from_filename(img_file) if img_file else ['en'])

        # 更新 row 的 values 和 tag
        self.tree.item(item_id, values=(expected_text, img_file, img_file, status, language_str))
        self.tree.item(item_id, tags=(color,))
        # 保证 tag 的样式已配置
        self.tree.tag_configure('green', foreground='green')
        self.tree.tag_configure('red', foreground='red')
        self.tree.tag_configure('neutral', foreground='gray')

        # 滚动到最新项（可选）
        try:
            self.tree.see(item_id)
        except Exception:
            pass

    def process_data(self):
        """
        读取 csv/excel（或使用已有的 self.csv_data），
        然后按图片逐张识别，并即时更新每一行的状态到 UI（Treeview）。
        """
        import os, traceback
        import pandas as pd
        import numpy as np
        from PIL import Image
        from collections import defaultdict

        try:
            self.log_message("开始处理任务")
            self.status_var.set("准备读取数据...")

            # 如果已经有 parsed 的 csv_data，直接使用
            if getattr(self, "csv_data", None):
                csv_rows = self.csv_data
            else:
                # 尝试从一些可能的路径属性读取文件
                csv_path = getattr(self, "csv_path", None) or getattr(self, "input_path", None) or getattr(self,
                                                                                                           "file_path",
                                                                                                           None)
                if not csv_path:
                    self.log_message("未指定 csv/excel 文件路径（属性 csv_path / input_path / file_path）。")
                    self.root.after(0, self.status_var.set, "错误：未指定 CSV/Excel 文件路径")
                    return

                # 读取文件（尝试多种方式/编码）
                df = None
                try:
                    if csv_path.lower().endswith((".xls", ".xlsx")):
                        df = pd.read_excel(csv_path)
                    else:
                        # 尝试常见编码
                        tried = False
                        for enc in ("utf-8", "gbk", "latin1"):
                            try:
                                df = pd.read_csv(csv_path, encoding=enc)
                                tried = True
                                break
                            except Exception:
                                continue
                        if not tried:
                            # 最后兜底再让 pandas 猜测
                            df = pd.read_csv(csv_path, encoding_errors='replace')
                except Exception as e:
                    self.log_message(f"读取文件失败: {e}")
                    self.root.after(0, self.status_var.set, f"读取文件失败: {e}")
                    return

                # 规范化列名（小写）并寻找 text/file 列
                df_columns = {c.lower(): c for c in df.columns}
                text_col = None
                file_col = None
                # 常见可能的列名
                for name in ("text", "content", "expected", "expected_text"):
                    if name in df_columns:
                        text_col = df_columns[name]
                        break
                for name in ("file", "img", "image", "filename", "path"):
                    if name in df_columns:
                        file_col = df_columns[name]
                        break
                # 如果没找到，尽量取第一两列
                if not text_col:
                    # try guess: any column containing 'text' substring
                    for k in df_columns:
                        if "text" in k:
                            text_col = df_columns[k]
                            break
                if not file_col:
                    for k in df_columns:
                        if any(x in k for x in ("file", "img", "image", "path", "name")):
                            file_col = df_columns[k]
                            break

                if not text_col or not file_col:
                    # 兜底：使用第一列为 text，第二列为 file（若存在）
                    cols = list(df.columns)
                    if len(cols) >= 2:
                        text_col, file_col = cols[0], cols[1]
                    else:
                        self.log_message("表格中无法识别 'text' 或 'file' 列。")
                        self.root.after(0, self.status_var.set, "错误：表格缺少必要列")
                        return

                # 构造 csv_rows 为 list[dict]
                csv_rows = []
                for _, r in df.iterrows():
                    txt = "" if pd.isna(r.get(text_col)) else str(r.get(text_col))
                    f = "" if pd.isna(r.get(file_col)) else str(r.get(file_col))
                    # 跳过空白行
                    if not txt.strip() and not f.strip():
                        continue
                    csv_rows.append({"text": txt, "file": f})
                self.csv_data = csv_rows  # 保存回属性以便其他方法使用

            # 如果没有行
            if not csv_rows:
                self.log_message("没有待处理的行")
                self.root.after(0, self.status_var.set, "表格为空")
                return

            # 插入初始行（等待处理）
            if hasattr(self, "insert_initial_rows"):
                self.root.after(0, self.insert_initial_rows)
            else:
                # 最小实现：在 Treeview 中插入每一行
                def _minimal_insert_rows():
                    try:
                        for item in getattr(self, "tree").get_children():
                            getattr(self, "tree").delete(item)

                        self.row_items = []
                        self.rows_by_file = defaultdict(list)
                        for i, row in enumerate(self.csv_data):
                            expected_text = row.get("text", "")
                            img_file = row.get("file", "")
                            lang_list = self.get_language_from_filename(img_file) if img_file else ["en"]
                            language_str = ", ".join(lang_list)
                            item_id = self.tree.insert("", "end", values=(
                                expected_text, img_file, img_file, "等待处理", language_str), tags=("neutral",))
                            self.row_items.append(item_id)
                            self.rows_by_file[img_file].append(i)

                        self.tree.tag_configure("green", foreground="green")
                        self.tree.tag_configure("red", foreground="red")
                        self.tree.tag_configure("neutral", foreground="gray")
                    except Exception as e:
                        self.log_message(f"_minimal_insert_rows 错误: {e}")

                self.root.after(0, _minimal_insert_rows)

            # 初始化统计
            self.total_count = 0
            self.success_count = 0
            self.fail_count = 0
            self.ocr_results = getattr(self, "ocr_results", {})

            if not getattr(self, "rows_by_file", None):
                from collections import defaultdict as _dd
                self.rows_by_file = _dd(list)
                for i, row in enumerate(self.csv_data):
                    self.rows_by_file[row.get("file", "")].append(i)

            files_list = list(self.rows_by_file.keys())
            total_rows = len(self.csv_data)
            processed_rows = 0

            # 主循环：按图片逐张处理
            for img_file in files_list:
                if getattr(self, "stop_processing_flag", False):
                    self.log_message("处理已被请求停止")
                    self.root.after(0, self.status_var.set, "已停止")
                    break

                row_indices = self.rows_by_file.get(img_file, [])
                image_path = os.path.join(getattr(self, "image_dir", ""), img_file) if img_file else ""

                if not img_file or not os.path.isfile(image_path):
                    for idx in row_indices:
                        self.fail_count += 1
                        self.total_count += 1
                        processed_rows += 1
                        self.root.after(0, getattr(self, "_apply_row_update"), idx, "失败 (图片不存在)", "red")
                        self.root.after(0, getattr(self, "progress_var").set, int(processed_rows / total_rows * 100))
                        self.root.after(0, self.update_stats)
                    continue

                lang_list = self.get_language_from_filename(img_file)
                try:
                    reader = self.get_reader(lang_list)
                except Exception as e:
                    self.log_message(f"get_reader 错误: {e}")
                    for idx in row_indices:
                        self.fail_count += 1
                        self.total_count += 1
                        processed_rows += 1
                        self.root.after(0, getattr(self, "_apply_row_update"), idx, "失败 (初始化 OCR 失败)", "red")
                        self.root.after(0, getattr(self, "progress_var").set, int(processed_rows / total_rows * 100))
                        self.root.after(0, self.update_stats)
                    continue

                self.root.after(0, self.status_var.set, f"正在识别: {img_file} ({', '.join(lang_list)})")
                self.log_message(f"识别图片 {img_file} 语言 {lang_list}", is_ocr_log=True)

                try:
                    img = Image.open(image_path).convert("RGB")
                    img_arr = np.array(img)

                    try:
                        ocr_results_raw = reader.readtext(img_arr, detail=1)
                    except Exception as e:
                        self.log_message(f"读取文本失败: {e}")
                        ocr_results_raw = []

                    recognized_text = " ".join(
                        [str(res[1]).strip() for res in ocr_results_raw if len(res) >= 2 and str(res[1]).strip()])

                    if ocr_results_raw:
                        self.log_message("识别结果:", is_recognition_result=True)
                        for i, res in enumerate(ocr_results_raw, 1):
                            if len(res) >= 2:
                                self.log_message(f"  结果{i}: {res[1]}", is_recognition_result=True)
                    else:
                        self.log_message("识别结果: (无内容)", is_recognition_result=True)

                    if hasattr(self, "correct_common_errors"):
                        recognized_text = self.correct_common_errors(recognized_text, lang_list)

                    self.ocr_results[img_file] = recognized_text

                    # 对每一个引用该图片的 CSV 行进行比对并更新
                    for idx in row_indices:
                        expected_text = self.csv_data[idx].get("text", "")
                        status = "失败"
                        color = "red"
                        is_match = False

                        try:
                            # 步骤 1: 判断匹配状态
                            if self.text_match(expected_text, recognized_text, lang_list):
                                is_match = True
                                status = "成功"
                            elif self.find_fragments(expected_text, recognized_text, lang_list):
                                is_match = True
                                status = "成功 (片段)"

                        except Exception as e:
                            self.log_message(f"比对异常 idx={idx}: {e}")
                            status = "失败 (异常)"
                            is_match = False

                        # 步骤 2: 根据结果更新分类计数
                        if is_match:
                            self.success_count += 1
                            color = "green"
                        else:
                            self.fail_count += 1

                        # 步骤 3: 更新总数和进度
                        self.total_count += 1
                        processed_rows += 1

                        # 步骤 4: 更新UI
                        if hasattr(self, "_apply_row_update"):
                            self.root.after(0, self._apply_row_update, idx, status, color)

                        self.root.after(0, self.progress_var.set, int(processed_rows / total_rows * 100))
                        self.root.after(0, self.update_stats)

                except Exception as e:
                    tb = traceback.format_exc()
                    self.log_message(f"OCR 识别错误 {img_file}: {e}\n{tb}")
                    for idx in row_indices:
                        self.fail_count += 1
                        self.total_count += 1
                        processed_rows += 1
                        if hasattr(self, "_apply_row_update"):
                            self.root.after(0, self._apply_row_update, idx, "失败 (识别错误)", "red")
                        self.root.after(0, self.progress_var.set, int(processed_rows / total_rows * 100))
                        self.root.after(0, self.update_stats)

            if getattr(self, "stop_processing_flag", False):
                self.log_message("处理被中止")
                self.root.after(0, self.status_var.set, "已停止")
            else:
                self.log_message(f"处理完成，成功 {self.success_count}，失败 {self.fail_count}")
                self.root.after(0, self.status_var.set, f"处理完成，成功 {self.success_count}，失败 {self.fail_count}")
                self.root.after(0, self.progress_var.set, 100)

        except Exception as e:
            tb = traceback.format_exc()
            self.log_message(f"process_data 异常: {e}\n{tb}")
            self.root.after(0, self.status_var.set, f"错误: {e}")
        finally:
            try:
                if hasattr(self, "process_btn"):
                    self.root.after(0, self.process_btn.config, {"state": "normal"})
                if hasattr(self, "stop_btn"):
                    self.root.after(0, self.stop_btn.config, {"state": "disabled"})
            except Exception:
                pass

    def text_match(self, expected_text, ocr_text, lang_list):
        """改进的文本匹配方法，考虑语言特性"""
        # 标准化文本
        expected_normalized = self.normalize_text(expected_text, lang_list)
        ocr_normalized = self.normalize_text(ocr_text, lang_list)

        # 检查标准化后的文本是否匹配
        if expected_normalized.lower() in ocr_normalized.lower():
            return True

        # 对于日语，检查去除空格后的匹配
        if 'ja' in lang_list:
            expected_no_space = expected_normalized.replace(' ', '').replace('　', '')
            ocr_no_space = ocr_normalized.replace(' ', '').replace('　', '')
            if expected_no_space.lower() in ocr_no_space.lower():
                return True

        return False

    def normalize_text(self, text, lang_list):
        """标准化文本：全角转半角，处理常见OCR错误"""
        # 首先应用通用修正
        normalized = self.correct_common_errors(text, lang_list, True)

        # 对于日语，应用额外的标准化
        if 'ja' in lang_list:
            normalized = self.normalize_japanese_text(normalized)

        return normalized

    def normalize_japanese_text(self, text):
        """标准化日语文本：全角转半角，处理常见OCR错误"""
        # 全角英数字转半角
        full_to_half = {
            'Ａ': 'A', 'Ｂ': 'B', 'Ｃ': 'C', 'Ｄ': 'D', 'Ｅ': 'E', 'Ｆ': 'F', 'Ｇ': 'G',
            'Ｈ': 'H', 'Ｉ': 'I', 'Ｊ': 'J', 'Ｋ': 'K', 'Ｌ': 'L', 'Ｍ': 'M', 'Ｎ': 'N',
            'Ｏ': 'O', 'Ｐ': 'P', 'Ｑ': 'Q', 'Ｒ': 'R', 'Ｓ': 'S', 'Ｔ': 'T', 'Ｕ': 'U',
            'Ｖ': 'V', 'Ｗ': 'W', 'Ｘ': 'X', 'Ｙ': 'Y', 'Ｚ': 'Z',
            'ａ': 'a', 'ｂ': 'b', 'ｃ': 'c', 'ｄ': 'd', 'ｅ': 'e', 'ｆ': 'f', 'ｇ': 'g',
            'ｈ': 'h', 'ｉ': 'i', 'ｊ': 'j', 'ｋ': 'k', 'ｌ': 'l', 'ｍ': 'm', 'ｎ': 'n',
            'ｏ': 'o', 'ｐ': 'p', 'ｑ': 'q', 'ｒ': 'r', 'ｓ': 's', 'ｔ': 't', 'ｕ': 'u',
            'ｖ': 'v', 'ｗ': 'w', 'ｘ': 'x', 'ｙ': 'y', 'ｚ': 'z',
            '０': '0', '１': '1', '２': '2', '３': '3', '４': '4', '５': '5', '６': '6',
            '７': '7', '８': '8', '９': '9',
            '！': '!', '＂': '"', '＃': '#', '＄': '$', '％': '%', '＆': '&', '＇': "'",
            '（': '(', '）': ')', '＊': '*', '＋': '+', '，': ',', '－': '-', '．': '.',
            '／': '/', '：': ':', '；': ';', '＜': '<', '＝': '=', '＞': '>', '？': '?',
            '＠': '@', '［': '[', '＼': '\\', '］': ']', '＾': '^', '＿': '_', '｀': '`',
            '｛': '{', '｜': '|', '｝': '}', '～': '~', '　': ' '
        }

        # 常见日语OCR错误修正
        common_errors = {
            'ソ': 'ン',  # 片假名"so"和"n"容易混淆
            'ン': 'ソ',  # 反向修正
            'シ': 'ツ',  # 片假名"shi"和"tsu"容易混淆
            'ツ': 'シ',  # 反向修正
            'ウ': 'ワ',  # 片假名"u"和"wa"容易混淆
            'ワ': 'ウ',  # 反向修正
            'タ': '夕',  # 片假名"ta"和"夕"容易混淆
            '夕': 'タ',  # 反向修正
            'ダ': '夕',  # 浊音版本
            '夕': 'ダ',  # 反向修正
            'ヘ': 'へ',  # 片假名和平假名混淆
            'へ': 'ヘ',  # 反向修正
            'ベ': 'べ',  # 浊音版本
            'べ': 'ベ',  # 反向修正
            'ペ': 'ぺ',  # 半浊音版本
            'ぺ': 'ペ',  # 反向修正
            'ホ': '木',  # 片假名"ho"和汉字"木"容易混淆
            '木': 'ホ',  # 反向修正
            'ボ': '木',  # 浊音版本
            '木': 'ボ',  # 反向修正
            'ポ': '木',  # 半浊音版本
            '木': 'ポ',  # 反向修正
            'ユ': 'ュ',  # 片假名"yu"和小"yu"容易混淆
            'ュ': 'ユ',  # 反向修正
            'ヨ': 'ョ',  # 片假名"yo"和小"yo"容易混淆
            'ョ': 'ヨ',  # 反向修正
            '川': '三',  # 汉字"川"和"三"容易混淆
            '三': '川',  # 反向修正
            'ミ': '三',  # 片假名"mi"和汉字"三"容易混淆
            '三': 'ミ',  # 反向修正
            'リ': 'り',  # 片假名和平假名混淆
            'り': 'リ',  # 反向修正
            'フ': 'ふ',  # 片假名和平假名混淆
            'ふ': 'フ',  # 反向修正
            'ブ': 'ぶ',  # 浊音版本
            'ぶ': 'ブ',  # 反向修正
            'プ': 'ぷ',  # 半浊音版本
            'ぷ': 'プ',  # 反向修正
            'ダウンロード': 'ダウンロード',  # 修正常见错误
            'ダフンロード': 'ダウンロード',
            'ダウンロ': 'ダウンロード',
            'ダフンロ': 'ダウンロード',
            '変扶': '変換',
            '対広': '対応',
            'ケイト': 'サイト',  # "site"的片假名
            '人気ケイト': '人気サイト',
            '全仲': '全体',  # 修复"全仲"->"全体"
            'ブレイ': 'プレイ',  # 修复"ブレイ"->"プレイ"
            'プレイ': 'ブレイ',  # 反向修正
            '5{': '5倍',  # 修复"5{"->"5倍"
            '速意': '速度',  # 修复"速意"->"速度"
            'MPA': 'MP4',  # 修复"MPA"->"MP4"
            'MP4': 'MPA',  # 反向修正
            '1,00O': '1,000',  # 修复"1,00O"->"1,000"
            '1,000': '1,00O',  # 反向修正
            '100': '1,000',  # 修复"100"->"1,000"
            '1,000': '100',  # 反向修正
            'Aluto': 'Auto',  # 修复"Aluto"->"Auto"
            't0': 'to',  # 修复"t0"->"to"
            'MP3IMPA': 'MP3/MP4',  # 修复"MP3IMPA"->"MP3/MP4"
            'YouTubeを': 'YouTubeを',  # 确保不会错误修正
            'YouTube': 'YouTube',  # 确保不会错误修正
            'HD/4K': 'HD/4K',  # 确保不会错误修正
            'in HD/4K': 'in HD/4K',  # 确保不会错误修正
            'supported': 'supported',  # 确保不会错误修正
            'popular': 'popular',  # 确保不会错误修正
            'sites': 'sites',  # 确保不会错误修正
            '以上': '以上',  # 确保不会错误修正
            'の人気': 'の人気',  # 确保不会错误修正
            'サイト': 'サイト',  # 确保不会错误修正
            'に対応': 'に対応',  # 确保不会错误修正
            'ダウンロード速度': 'ダウンロード速度',  # 确保不会错误修正
            '一度に': '一度に',  # 确保不会错误修正
            'プレイリスト': 'プレイリスト',  # 确保不会错误修正
            '全体': '全体',  # 确保不会错误修正
        }

        # 先应用常见错误修正
        for wrong, correct in common_errors.items():
            text = text.replace(wrong, correct)

        # 然后转换全角字符为半角
        normalized = ''.join(full_to_half.get(char, char) for char in text)

        return normalized

    def find_fragments(self, expected_text, ocr_text, lang_list):
        """尝试在OCR文本中查找预期文本的片段"""
        # 如果OCR文本为空，直接返回False
        if not ocr_text:
            return False

        # 标准化文本
        expected_normalized = self.normalize_text(expected_text, lang_list)
        ocr_normalized = self.normalize_text(ocr_text, lang_list)

        # 将预期文本拆分为单词
        if 'ja' in lang_list:
            # 日语分词：使用更宽松的正则表达式
            expected_words = re.findall(r'[\w\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff]+', expected_normalized.lower())
        else:
            # 英语分词
            expected_words = re.findall(r'\b\w+\b', expected_normalized.lower())

        # 如果预期文本太短，直接返回False
        if len(expected_words) < 2:
            return False

        # 检查所有单词是否按顺序出现在OCR文本中
        ocr_lower = ocr_normalized.lower()
        last_index = -1

        for word in expected_words:
            index = ocr_lower.find(word, last_index + 1)
            if index == -1:
                return False
            last_index = index

        return True

    def correct_common_errors(self, text, lang_list, is_normalize=False):
        """修正常见的OCR识别错误，考虑语言特性"""
        if text is None:
            return ""

        if not self.auto_correct_var.get() and not is_normalize:
            return text

        # 常见错误映射表
        corrections = {
            "．": ".",  # 全角句点替换为半角
            "，": ",",  # 全角逗号替换为半角
            "２": "2", "３": "3", "４": "4", "５": "5",
            "６": "6", "７": "7", "８": "8", "９": "9",
            "Ｏ": "O",  # 全角O替换为半角O
            "０": "0",  # 全角0替换为半角0
            "Ａ": "A",  # 全角A替换为半角A
            "Ｉ": "I",  # 全角I替换为半角I
            "{": "倍",  # 修复"5{"->"5倍"
            "意": "度",  # 修复"速意"->"速度"
            "仲": "体",  # 修复"全仲"->"全体"
            "ブ": "プ",  # 修复"ブレイ"->"プレイ"
            "O": "0",  # 在数字上下文中，O可能是0
            "l": "I",  # 小写l和大写I容易混淆
            "I": "l",  # 反向修正
            "4l": "Al",  # 新增：修正4luto->Aluto
            "4L": "AL",  # 大写版本修正
        }

        # 处理特定单词的错误
        word_corrections = {
            " ln ": " in ",
            " lnto ": " into ",
            " lt ": " it ",
            " lts ": " its ",
            " l'm ": " I'm ",
            " l'II ": " I'll ",
            " thls ": " this ",
            " functlon ": " function ",
            " varlable ": " variable ",
            " strlng ": " string ",
            " boo1 ": " bool ",
            " c1ass ": " class ",
            " deflnltlon ": " definition ",
            " exarnple ": " example ",
            " prograrn ": " program ",
            "Aluto": "Auto",  # 修复 Auto → Aluto
            "4luto": "Auto",  # 新增：直接修正4luto->Auto
            "4luto ": "Auto ",  # 带空格版本
            "4luto.": "Auto.",  # 带句点版本
            "4Luto": "Auto",  # 首字母大写版本
            " t0 ": " to ",  # 修复 to → t0
            "MPA": "MP4",  # 修复 MP4 → MPA
            "00O": "000",  # 修复 000 → 00O
            "1,00O": "1,000",  # 修复 1,000 → 1,00O
            "100": "1,000",  # 修复 100 → 1,000
            "ダフンロード": "ダウンロード",  # 日语常见错误修正
            "ダフンロ": "ダウンロード",
            "変扶": "変換",
            "対広": "対応",
            "ケイト": "サイト",
            "全仲": "全体",
            "ブレイ": "プレイ",
            "5{": "5倍",
            "速意": "速度",
            "MP3IMPA": "MP3/MP4",
            "Convert YouTube t0 MP3IMPA instantly": "Convert YouTube to MP3/MP4 instantly",
            "Download YouTube to MPA in HD/4K": "Download YouTube to MP4 in HD/4K",
            "1,00O+ popular sites supported": "1,000+ popular sites supported",
        }

        # 应用字符级修正
        for wrong, correct in corrections.items():
            text = text.replace(wrong, correct)

        # 应用单词级修正
        for wrong, correct in word_corrections.items():
            if wrong in text:
                text = text.replace(wrong, correct)

        # 特殊处理数字和字母混淆
        # 修复类似 "1,00O" -> "1,000"
        if re.search(r'\d,\d{2}O', text):
            text = re.sub(r'(\d),(\d{2})O', r'\1,\2', text)  # 修正正则表达式

        # 修复类似 "MPA" -> "MP4" (当在数字上下文中)
        if re.search(r'MPA\b', text) and any(char.isdigit() for char in text):
            text = re.sub(r'MPA\b', 'MP4', text)

        return text

    def update_results_table(self):
        # 清空表格
        for item in self.tree.get_children():
            self.tree.delete(item)

        # 添加新结果
        for result in self.results:
            self.tree.insert("", "end", values=(
                result['text'],
                result['file'],
                result['img'],
                result['status'],
                result['language']
            ), tags=(result['color'],))

        # 设置行颜色
        self.tree.tag_configure('green', foreground='green')
        self.tree.tag_configure('red', foreground='red')


if __name__ == "__main__":
    root = tk.Tk()
    app = OCRBatchProcessor(root)
    root.mainloop()